package rpc

import (
	"encoding/json"
	"errors"
	"fmt"
	corev1 "gluenet/internal/agent/api/core/v1"
	v1 "gluenet/internal/agent/api/rpc/v1"
	"gluenet/internal/agent/driver/rpcserver"
	"gluenet/pkg/agent/types"
)

const (
	RPC_VERSION = `v2`
)

type Request struct {
	c *RpcClient

	rpcPrefix string
	rpcApi    string

	guid     string
	resource string
	name     string

	opts interface{}
	spec interface{}

	err error
}

func NewRequest(c *RpcClient) *Request {

	rpcPerfix := fmt.Sprint(c.target, v1.SEP)
	r := &Request{
		c:         c,
		rpcPrefix: rpcPerfix,
		guid:      `default`,
	}

	return r
}

func (r *Request) Api(rpcApi string) *Request {
	if r.err != nil {
		return r
	}

	r.rpcApi = rpcApi
	return r
}

func (r *Request) Guid(guid string) *Request {
	if r.err != nil {
		return r
	}

	r.guid = guid
	return r
}

func (r *Request) Resource(resource string) *Request {
	if r.err != nil {
		return r
	}

	r.resource = resource
	return r
}

func (r *Request) Name(name string) *Request {
	if r.err != nil {
		return r
	}

	r.name = name
	return r
}

func (r *Request) Options(opts interface{}) *Request {
	if r.err != nil {
		return r
	}

	r.opts = opts
	return r
}

func (r *Request) Spec(spec interface{}) *Request {
	if r.err != nil {
		return r
	}

	r.spec = spec
	return r
}

func (r *Request) newRpcRequest() (req *rpcserver.Request, err error) {

	glueObj := corev1.GluenetRequest{
		GluenetMeta: corev1.GluenetMeta{
			GluenetGuid:    r.guid,
			GluenetKind:    r.resource,
			GluenetVersion: RPC_VERSION},
		Spec:    r.spec,
		Options: r.opts,
	}

	req = new(rpcserver.Request)
	if req.Data, err = json.Marshal(glueObj); err != nil {
		return
	}
	req.Pattern = fmt.Sprint(r.rpcPrefix, r.rpcApi)
	return
}

func (r *Request) Do() Result {
	var result Result

	err := r.request(func(respBody []byte) {
		result.body = respBody
	})

	if err != nil {
		return Result{err: err}
	}

	return result
}

func (r *Request) DryDo() (*rpcserver.Request, error) {
	return r.newRpcRequest()
}

func (r *Request) request(fn func(respBody []byte)) error {
	if r.err != nil {
		return r.err
	}

	client := r.c.client
	if client == nil {
		return errors.New(`rpc client is not inited`)
	}

	req, err := r.newRpcRequest()
	if err != nil {
		return err
	}

	resp, err := client.Request(req.Pattern, req.Data, TIME_OUT)
	if err != nil {
		return err
	}

	fn(resp.Data)

	return nil
}

type Result struct {
	body []byte
	err  error
}

func (r Result) Error() error {
	return r.err
}

func (r Result) Into(obj interface{}) error {
	if r.err != nil {
		return r.err
	}

	if len(r.body) == 0 {
		return ErrNoResponse
	}

	resp := new(types.CommonResp)
	resp.Data = obj
	if err := json.Unmarshal(r.body, resp); err != nil {
		return err
	}

	if resp.IsSuccess {
		return nil
	}

	switch resp.Message {
	case ErrNotExist.Error():
		return ErrNotExist
	case ErrDuplicated.Error():
		return ErrDuplicated
	default:
		return errors.New(resp.Message)
	}
}

func (r Result) IntoCommonResponse() error {
	if r.err != nil {
		return r.err
	}

	if len(r.body) == 0 {
		return ErrNoResponse
	}

	resp := new(types.CommonResp)
	if err := json.Unmarshal(r.body, resp); err != nil {
		return err
	}

	if !resp.IsSuccess {
		return errors.New(resp.Message)
	}

	return nil
}
